package wangjg.commons.dao;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Expression;
import org.hibernate.criterion.LogicalExpression;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.internal.CriteriaImpl;
import org.hibernate.transform.ResultTransformer;

import wangjg.commons.page.Page;
import wangjg.commons.springside.ReflectionUtils;

/**
 * 
 * @author wangjg
 *
 */
@SuppressWarnings("all")
public class HibernateDAOHelper {
	
	protected <ID> Criterion getInListCriterion(String propertyName, List<ID> idList, int rangeSize){
		if(idList.size() <= rangeSize){
			return Restrictions.in(propertyName, idList);
		}

		List<List<ID>> listList = new ArrayList<List<ID>>(); 
		int fromIndex = 0;
		while(fromIndex < idList.size()){
			int toIndex = fromIndex + rangeSize;
			if(toIndex > idList.size()){
				toIndex = idList.size();
			}
			List<ID> subList = idList.subList(fromIndex, toIndex);
			listList.add(subList);
			fromIndex = toIndex;
		}
		
		LogicalExpression le = Restrictions.or(
			Restrictions.in(propertyName, listList.get(0)),
			Restrictions.in(propertyName, listList.get(1))
		);
		
		for(int i=2; i<listList.size(); i++){
			le =  Restrictions.or(le, Restrictions.in(propertyName, listList.get(i)));
		}
		
		return le;
	}
	
	public static List pageQuery(AbstractBasicAO dao,Page page,final String hql, final Object... values){
		String countHQL = getCountHQL(hql);
		long count = (Long)dao.queryUnique(countHQL, values);
		page.setRowTotal(count);
		List list = dao.query(page.getStartRow(), page.getPageSize(),hql , values);
		return list;
	}
	
	private static String getCountHQL(String hql){
		String fromHql = hql;
		//select子句与order by子句会影响count查询,进行简单的排除.
		fromHql = "from " + StringUtils.substringAfter(fromHql, "from");
		fromHql = StringUtils.substringBefore(fromHql, "order by");
		String countHql = "select count(*) " + fromHql;
		return countHql;
	}
	/*
	 * 执行count查询获得本次Criteria查询所能获得的对象总数.去掉order by
	 * 2
	 */
	public static <T> T countNoOrder(final Criteria c) {
		CriteriaImpl impl = (CriteriaImpl) c;

		// 先把Projection、ResultTransformer、OrderBy取出来,清空三者后再执行Count操作
		Projection projection = impl.getProjection();
		ResultTransformer transformer = impl.getResultTransformer();

		List<CriteriaImpl.OrderEntry> orderEntries = null;
		
		orderEntries = (List) ReflectionUtils.getFieldValue(impl, "orderEntries");
		ReflectionUtils.setFieldValue(impl, "orderEntries", new ArrayList());

		// 执行Count查询
		Object o = c.setProjection(Projections.rowCount()).uniqueResult();
		T totalCount = (T)o;

		// 将之前的Projection,ResultTransformer和OrderBy条件重新设回去
		c.setProjection(projection);

		if (projection == null) {
			c.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
		}
		if (transformer != null) {
			c.setResultTransformer(transformer);
		}
		ReflectionUtils.setFieldValue(impl, "orderEntries", orderEntries);

		return totalCount;
	}
	
	//3
	public static <N extends Number> N count(Criteria criteria){
		N count = null;
		CriteriaImpl impl = (CriteriaImpl) criteria;
		Projection projection = impl.getProjection();//取出projection
		ResultTransformer resultTransformer = impl.getResultTransformer();
		List orderEntries = null;// 用来存放排序字段
		Integer firstResult = impl.getFirstResult();
		Integer maxResults = impl.getMaxResults();
		try {
			Field field = CriteriaImpl.class.getDeclaredField("orderEntries");
			field.setAccessible(true);
			orderEntries = (List) field.get(impl);// 将对象中的排序字段存入数组中
			
			field.set(criteria, new ArrayList());// 将排序字段设置为空
			criteria.setProjection(Projections.rowCount());
			
			criteria.setFirstResult(0);
			criteria.setMaxResults(Integer.MAX_VALUE);
			
			count = (N) criteria.uniqueResult();
			
			//////////////////////////////////////////
			criteria.setProjection(projection);// 重新设置回projection
			//重新设置回 ResultTransformer
			if (resultTransformer != null){
				criteria.setResultTransformer(resultTransformer);
			}
			field.set(criteria, orderEntries);// 重新设置回排序字段
			
			if(firstResult!=null){
				criteria.setFirstResult(firstResult);
			}
			if(maxResults!=null){
				criteria.setMaxResults(maxResults);
			}
			
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return count;
	}
	
	/**
	 * 
	 * @param criteria
	 * @param exampleEntity
	 * @param fuzzy 模糊查询
	 */
	public static void setCriteriaByExample(Criteria criteria, Object exampleEntity, boolean fuzzy) {
		if(exampleEntity==null)
			throw new NullPointerException("exampleEntity is null.");
		Map map;
		try {
			map = PropertyUtils.describe(exampleEntity);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		Set<Entry<String, Object>> entrySet = map.entrySet();   
		for (Map.Entry<String, Object> entry : entrySet) {
		    String name=entry.getKey();
		    Object value=entry.getValue();
		    if(value==null){
		    	continue;
		    }
		    if(!PropertyUtils.isWriteable(exampleEntity, name)){
		    	continue;
		    }
		    Class type=value.getClass();
		    if(!fuzzy){
				criteria.add(Restrictions.eq(name, value));
			}else{
				if(type.equals(java.lang.String.class)){//字符串模糊查询
					String str=(String)value;
					if(str.length()>0)
						criteria.add(Restrictions.like(name, "%"+ str+"%"));
				}else if(type.equals(java.util.Date.class)){//时间只精确到日期，一般查询不会输入秒的
					java.util.Date date=(java.util.Date)value;
					java.util.Calendar calendar=java.util.Calendar.getInstance();
					calendar.setTime(date);
					calendar.set(java.util.Calendar.HOUR_OF_DAY, 0);
					calendar.set(java.util.Calendar.MINUTE, 0);
					calendar.set(java.util.Calendar.SECOND, 0);
					Date date1 = calendar.getTime();
					calendar.add(java.util.Calendar.DAY_OF_MONTH, 1);
					calendar.set(java.util.Calendar.MILLISECOND, -1);
					Date date2 = calendar.getTime();
					criteria.add(Expression.between(name,date1,date2));
				}else if(isCustomType(type)){
					Criteria c=criteria.createCriteria(name);
					setCriteriaByExample(c, value, fuzzy);
				}else{
					criteria.add(Restrictions.eq(name, value));
				}
			}
		}
		return ;
	}
	
	/**
	 *@deprecated 好的方法还没有
	 */
	private static boolean isCustomType(Class type){
		return (!type.getName().startsWith("java"));
	}
}
